{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 139,
   "metadata": {
    "tags": [
     "remove-cell"
    ]
   },
   "outputs": [],
   "source": [
    "import sys\n",
    "import os\n",
    "if not any(path.endswith('textbook') for path in sys.path):\n",
    "    sys.path.append(os.path.abspath('../../..'))\n",
    "from textbook_utils import *"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "(ch:files_formats)=\n",
    "# File Formats"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A *file format* describes how data are stored on a computer's disk or other storage device.\n",
    "Understanding the file format helps us figure out how to read the data into Python in order to work with it\n",
    "as a data table.\n",
    "In this section, we introduce several popular formats used to store data tables.\n",
    "These are all plain-text formats, meaning they are easy for us to read with a text editor like VS Code, Sublime, Vim, or Emacs."
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ":::{note}\n",
    "\n",
    "The file format and the *structure* of the data are two\n",
    "different things.\n",
    "We consider the data structure to be a mental representation of the data that tells us what kinds of operations we can do.\n",
    "For example, a table structure corresponds to data values arranged in rows\n",
    "and columns.\n",
    "But the same table can be stored in many different types of file formats.\n",
    "\n",
    ":::"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The first format we describe is the delimited file format."
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Delimited Format\n",
    "\n",
    "Delimited formats use a specific character to separate data values.\n",
    "Usually, these separators are either a comma (comma-separated-values, or CSV\n",
    "for short), a tab (tab-separated values or TSV), whitespace, or a colon. These\n",
    "formats are natural for storing data that have a table structure.\n",
    "Each line in the file represents a record, which is delimited by newline (`\\n` or `\\r\\n`) characters. \n",
    "And within a line, the record's\n",
    "information is delimited by the comma character (`,`) for CSV or the tab\n",
    "character (`\\t`) for TSV, and so on. The first line of these files often contains the names of the table's columns/features."
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The San Francisco restaurant scores are stored in CSV-formatted files. \n",
    "Let's display the first few lines of the _inspections.csv_ file. In Python, the built-in `pathlib` library has a useful `Path` object to specify paths to files and folders that work across platforms.\n",
    "This file is within the _data_ folder, so we use `Path()` to create the full pathname:  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 140,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pathlib import Path\n",
    "\n",
    "# Create a Path pointing to our data file\n",
    "insp_path = Path() / 'data' / 'inspections.csv'"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ":::{note} \n",
    "\n",
    "Paths are tricky when working across different\n",
    "operating systems (OSs). For instance, a typical path in Windows might look like\n",
    "_C:\\files\\data.csv_, while a path in Unix or macos might look like\n",
    "_~/files/data.csv_. Because of this, code that works on one OS can fail to run\n",
    "on other OSs.\n",
    "\n",
    "The `pathlib` Python library was created to avoid OS-specific path issues. By\n",
    "using it, the code shown here is more *portable*---it works across Windows,\n",
    "macos, and Unix.\n",
    "\n",
    ":::"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The `Path` object in the following code has many useful methods, such as `read_text()`, which reads in the entire contents of the file as a string:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 145,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\"business_id\",\"score\",\"date\",\"type\"\n",
      "19,\"94\",\"20160513\",\"routine\"\n",
      "19,\"94\",\"20171211\",\"routine\"\n",
      "24,\"98\",\"20171101\",\"routine\"\n",
      "24,\"98\",\"20161005\",\"routine\"\n"
     ]
    }
   ],
   "source": [
    "text = insp_path.read_text()\n",
    "# Print first five lines\n",
    "print('\\n'.join(text.split('\\n')[:5]))"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Notice that the field names appear in the first line of the file; these names are comma separated and in\n",
    "quotes. We see four fields: the business identifier, the\n",
    "restaurant's score, the date of the inspection, and the type of inspection.\n",
    "Each line in the file corresponds to one inspection, and the ID, score, date,\n",
    "and type values are separated by commas. In addition to identifying the file\n",
    "format, we also want to identify the format of the features. We see two things\n",
    "of note: the scores and dates both appear as strings. We will want to convert\n",
    "the scores to numbers so that we can calculate summary statistics and create visualizations. \n",
    "And we will convert the date into a date-time format so\n",
    "that we can make time-series plots. We show how to carry out these\n",
    "transformations in {numref}`Chapter %s <ch:wrangling>`."
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Displaying the first few lines of a file is something we'll do often, so we create a function as a shortcut:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 147,
   "metadata": {},
   "outputs": [],
   "source": [
    "def head(filepath, n=5, width=-1):\n",
    "    '''Prints the width characters of first n lines of filepath'''\n",
    "    with filepath.open() as f:\n",
    "        for _ in range(n):\n",
    "            (print(f.readline(), end='') if width < 0  \n",
    "             else print(f.readline()[:width]))"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ":::{note}\n",
    "\n",
    "People often confuse CSV and TSV files with spreadsheets. This is in part\n",
    "because most spreadsheet software (like Microsoft Excel) will automatically\n",
    "display a CSV file as a table in a workbook. Behind the scenes, Excel looks at\n",
    "the file format and encoding just like we've done in this section. However,\n",
    "Excel files have a different format than CSV and TSV files, and we need to use\n",
    "different `pandas` functions to read these formats into Python.\n",
    "\n",
    ":::"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "All three of the restaurant source files are CSV formatted. In contrast, the DAWN source file has a fixed-width format. We describe this kind of formatting next."
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Fixed-Width Format\n",
    "\n",
    "The fixed-width format (FWF) does not use delimiters to separate data\n",
    "values. Instead, the values for a specific field appear in the exact same\n",
    "position in each line. The DAWN source file has this format.\n",
    "Each line in the file is very long. For display purposes, \n",
    "we only show the first few characters from the first five lines in the file: "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 148,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "     1 2251082    .9426354082   3 4 1 2201141 2 865 105 1102005 1\n",
      "     2 2291292   5.9920106887   911 1 3201134 12077  81  82 283-8\n",
      "     3 7 7 251   4.7231718669   611 2 2201143 12313   1  12  -7-8\n",
      "     410 8 292   4.0801470012   6 2 1 3201122 1 234 358  99 215 2\n",
      "     5 122 942   5.1777093467  10 6 1 3201134 3 865 105 1102005 1\n"
     ]
    }
   ],
   "source": [
    "dawn_path = Path() / 'data' / 'DAWN-Data.txt'\n",
    "head(dawn_path, width=65)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Notice how the values appear to align from one row to the next. \n",
    "For example, there is a decimal point in the same position (the 19th character) in each line. \n",
    "Notice also that some of the values seem to be squished together, and\n",
    "we need to know the exact position of each piece of information in\n",
    "a line in order to make sense of it. SAMHSA provides a 2,000-page [codebook](https://www.datafiles.samhsa.gov/dataset/drug-abuse-warning-network-2011-dawn-2011-ds0001) with\n",
    "all of this information, including some basic checks, so that we can confirm that we have correctly\n",
    "read the file. For instance, the codebook tells us that the age field appears in positions 34–35 and is coded in intervals from\n",
    "1 to 11. The first two records shown in the preceding code have age categories of 4 and 11;\n",
    "the codebook tells us that a 4 stands for the age bracket \"6 to 11\" and 11 is for \"65+.\""
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ":::{note}\n",
    "\n",
    "A widely adopted convention is to use the filename extension, such as\n",
    "_.csv_, _.tsv_, and _.txt_, to indicate the format of the contents of the file.\n",
    "Filenames that end with _.csv_ are expected to contain comma-separated values, and those ending with\n",
    "_.tsv_ are expected to contain tab-separated values; _.txt_ generally denotes plain text without a designated format. \n",
    "However, these extension names are only suggestions. Even if a file has a _.csv_ extension,\n",
    "the actual contents might not be formatted properly! It's a good practice to\n",
    "inspect the contents of the file before loading it into a dataframe. If the\n",
    "file is not too large, you can open and examine it with a plain-text editor.\n",
    "Otherwise, you can view a couple of lines using `.readline()` or shell\n",
    "command. \n",
    "\n",
    ":::"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Other plain-ext formats that are popular include hierarchical formats and loosely formatted text (in contrast to formats that directly support table structures). These are covered in greater detail in other chapters, but for completeness, we briefly describe them here."
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Hierarchical Formats\n",
    "\n",
    "Hierarchical formats store data in a nested form. For instance,\n",
    "JavaScript Object Notation (JSON), which is commonly used for communication by web servers, includes key-value pairs and arrays that can be nested, similar to a Python dictionary. The XML and HTML are other common formats for storing documents on the internet. Like JSON, these files also have a hierarchical, key-value format. We cover both formats (JSON and XML) in more detail in {numref}`Chapter %s <ch:web>`.\n",
    "\n",
    "Next, we briefly describe other plain-text files that don't fall into any of the previous categories but still have some structure to them that enables us to read and extract information."
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Loosely Formatted Text\n",
    "\n",
    "Web logs, instrument readings, and program logs typically provide data in plain text.  For example, here is one line of a web log (we've split it across multiple lines for readability). It contains information such as the date, time, and type of request made to a website:\n",
    "\n",
    "```\n",
    "169.237.46.168 - -\n",
    "[26/Jan/2004:10:47:58 -0800]\"GET /stat141/Winter04 HTTP/1.1\" 301 328\n",
    "\"http://anson.ucdavis.edu/courses\"\n",
    "\"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; .NET CLR 1.1.4322)\"\n",
    "```\n",
    "\n",
    "There are organizational patterns present, but not in a simple delimited format. This is what we mean by \"loosely formatted.\" We see that the date and time appear between square brackets, and the type of request (GET in this case) follows the date-time information and appears in quotes. In {numref}`Chapter %s <ch:text>`, we use these observations about the web log's format and string manipulation tools to extract values of interest into a data table."
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As another example, here is a single record taken from a wireless device log. The device reports the timestamp, the identifier, its location, and the signal strengths that it picks up from other devices. This information uses a combination of formats: key-value pairs, semicolon-delimited values, and comma-delimited values: \n",
    "\n",
    "```\n",
    "t=1139644637174;id=00:02:2D:21:0F:33;pos=2.0,0.0,0.0;degree=45.5;\n",
    "00:14:bf:b1:97:8a=-33,2437000000,3;00:14:bf:b1:97:8a=-38,2437000000,3;\n",
    "```\n",
    "\n",
    "Like with the web logs, we can use string manipulation and the patterns in the records to extract features into a table."
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We have primarily introduced formats for plain-text data that are widely used for storing and exchanging tables. The CSV format is the most common, but others, such as tab-separated and fixed-width formats, are also prevalent. And, there are many types of file formats that store data! \n",
    "\n",
    "So far, we have used the term _plain text_ to broadly cover formats that can be viewed with a text editor. However, a plain-text file may have different encodings, and if we don't specify the encoding correctly, the values in the dataframe might contain gibberish. We give an overview of file encoding next.  "
   ]
  }
 ],
 "metadata": {
  "celltoolbar": "Tags",
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.11"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
